/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 8421 BCD 编解码
 */
public class BCDUtils
{
	private static final Logger logger = LogManager.getLogger();

	/**
	 * 将 "1,0" 转换为 00010000, "0,9" 转换为 00001001
	 *
	 * <pre>
	 *   toBinary('1', '2');  // 输出: 0x12 (00010002)
	 * </pre>
	 *
	 * @param b1 高字节
	 * @param b2 低字节
	 * @return 二进制字节
	 */
	public static byte toBinary(byte b1, byte b2) throws Exception {
		if (b1 >= '0' && b1 <= '9') {
			b1 = (byte) (b1 - '0');
		} else {
			throw new Exception(String.format(
					"<%02x>不是有效的 BCD 字符[0-9]", b1));
		}
		if (b2 >= '0' && b2 <= '9') {
			b2 -= '0';
		} else {
			throw new Exception(String.format(
					"<%02x>不是有效的 BCD 字符[0-9]", b2));
		}
		return (byte) ((b1 << 4) | b2);
	}

	/**
	 * 一个方便的函数，可以这样使用:
	 *
	 * <pre>
	 *   toBinary('a', 'b');
	 * </pre>
	 *
	 * @param c1 字符 1
	 * @param c2 字符 2
	 * @return 二进制字节
	 * @throws Exception 异常
	 */
	public static byte toBinary(char c1, char c2) throws Exception {
		return toBinary((byte) c1, (byte) c2);
	}

	/**
	 * BCD 压缩编码，可以将 4 个字节的 ASCII 数字编码为 2 个字节的二进制.
	 * 例如 '1234' 编码为 '0001 0002 0003 0004'.
	 *
	 * @param input 输入字节，例如"ABCDEF0123456789"
	 * @param length 数据长度
	 * @return 编码后的字节
	 */
	public static byte[] encode(byte[] input, int length) {
		if (length <= 0) {
			length = input.length;
		}
		if (length % 2 != 0) {
			logger.error("BCDEncode 的输入数据长度[{}]不是2的整倍数.", length);
			return null;
		}
		byte[] result = new byte[length / 2];
		for (int i = 0; i < length / 2; i++) {
			try {
				result[i] = toBinary(input[i * 2], input[i * 2 + 1]);
			} catch (Exception e) {
				logger.catching(e);
				return null;
			}
		}
		return result;
	}

	/**
	 * BCD 压缩编码，可以将 4 个字节的 ASCII 数字编码为 2 个字节的二进制.
	 * 例如 '1234' 编码为 '0001 0002 0003 0004'.
	 *
	 * @param input 输入字符串，例如"ABCDEF0123456789"
	 * @return 编码后的字节
	 */
	public static byte[] encode(String input) {
		return encode(input.getBytes(), 0);
	}

	/**
	 * BCD 压缩编码，可以将 4 个字节的 ASCII 数字编码为 2 个字节的二进制.
	 * 例如 '1234' 编码为 '0001 0002 0003 0004'.
	 *
	 * @param input 输入字节
	 * @param length 数据长度
	 * @param left_pad 左补零 或 右补零
	 * @return 编码后的字节
	 */
	public static byte[] encode(
			byte[] input, int length, boolean left_pad) {
		if (length <= 0) {
			length = input.length;
		}
		if (length % 2 == 0) {
			return encode(input, length);
		}
		byte[] new_bytes = new byte[length + 1];
		if (left_pad) {
			new_bytes[0] = '0';
			System.arraycopy(input, 0, new_bytes, 1, length);
		} else {
			System.arraycopy(input, 0, new_bytes, 0, length);
			new_bytes[length] = '0';
		}
		return encode(new_bytes, 0);
	}

	/**
	 * BCD 压缩编码，将 10 编码为 '0001 0000'
	 *
	 * @param input 数字
	 * @param left_pad 左补零 或 右补零
	 * @return 编码后的字节
	 */
	public static byte[] encode(long input, boolean left_pad) {
		String str = Long.toString(input);
		if (str.length() % 2 != 0) {
			str = (left_pad) ? "0" + str : str + "0";
		}
		return encode(str.getBytes(), 0);
	}

	/**
	 * 解码压缩 BCD.
	 *
	 * <p>
	 * 压缩 BCD 是二进制编码十进制的一种，其采用 1 个字节表示 2 个十进制数，例如：
	 * </p>
	 * <pre>
	 *    0001 0010
	 * </pre>
	 * <p>
	 * 表示：12  (0001 表示 1， 0010 表示 2).
	 * </p>
	 * <p>
	 * 需要注意的是本函数按照 8421 BCD 解码，十进制中最大的数值为 9，
	 * 用二进制表示是 1001. 超出这个范围则认为是非法的.
	 * </p>
	 *
	 * @param input 输入 BCD 字节序列
	 * @param length input 长度
	 * @return 解码后的数字
	 */
	public static long decode(byte[] input, int length) {
		if (length <= 0) {
			length = input.length;
		}
		long result = 0;
		for (int i = 0; i < length; i++) {
			int high = (input[i] >> 4) & 0x0F;
			int lower = input[i] & 0x0F;
			if (high > 9 || lower > 9) {
				logger.warn("BCD 序列[{}]第[{}]个字节高位或低位值大于 9.",
						ByteUtils.format(input, 0, length), i);
				return -1;
			}
			result = result * 100 + high * 10 + lower;
		}
		return result;
	}

}
